<?php

namespace youmbe\phpword;

use Yii;
use yii\helpers\ArrayHelper;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\i18n\Formatter;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Style\ListItem as ListItemStyle;
use PhpOffice\Common\Font;
use PhpOffice\PhpWord\SimpleType\LineSpacingRule;
use PhpOffice\PhpWord\SimpleType\TextAlignment;
use PhpOffice\PhpWord\SimpleType\Jc;

/**
 * Word Widget for generate Word File or for load Word File.
 *
 * Usage
 * -----
 *
 * Exporting data into an excel file.
 *
 * ~~~
 *
 * // export data only one worksheet.
 *
 * \youmbe\phpword\Word::widget([
 * 		'models' => $allModels,
 * 		'mode' => 'export', //default value as 'export'
 * 		'columns' => ['column1','column2','column3'],
 * 		//without header working, because the header will be get label from attribute label.
 * 		'headers' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
 * ]);
 *
 * \youmbe\phpword\Word::export([
 * 		'models' => $allModels,
 * 		'columns' => ['column1','column2','column3'],
 * 		//without header working, because the header will be get label from attribute label.
 * 		'headers' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
 * ]);
 *
 * // export data with multiple worksheet.
 *
 * \youmbe\phpword\Word::widget([
 * 		'isMultipleSection' => true,
 * 		'models' => [
 * 			'sheet1' => $allModels1,
 * 			'sheet2' => $allModels2,
 * 			'sheet3' => $allModels3
 * 		],
 * 		'mode' => 'export', //default value as 'export'
 * 		'columns' => [
 * 			'sheet1' => ['column1','column2','column3'],
 * 			'sheet2' => ['column1','column2','column3'],
 * 			'sheet3' => ['column1','column2','column3']
 * 		],
 * 		//without header working, because the header will be get label from attribute label.
 * 		'headers' => [
 * 			'sheet1' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
 * 			'sheet2' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
 * 			'sheet3' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3']
 * 		],
 * ]);
 *
 * \youmbe\phpword\Word::export([
 * 		'isMultipleSection' => true,
 * 		'models' => [
 * 			'sheet1' => $allModels1,
 * 			'sheet2' => $allModels2,
 * 			'sheet3' => $allModels3
 * 		],
 * 		'columns' => [
 * 			'sheet1' => ['column1','column2','column3'],
 * 			'sheet2' => ['column1','column2','column3'],
 * 			'sheet3' => ['column1','column2','column3']
 * 		],
 * 		//without header working, because the header will be get label from attribute label.
 * 		'headers' => [
 * 			'sheet1' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
 * 			'sheet2' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3'],
 * 			'sheet3' => ['column1' => 'Header Column 1','column2' => 'Header Column 2', 'column3' => 'Header Column 3']
 * 		],
 * ]);
 *
 * ~~~
 *
 * New Feature for exporting data, you can use this if you familiar yii gridview.
 * That is same with gridview data column.
 * Columns in array mode valid params are 'attribute', 'header', 'format', 'value', and footer (TODO).
 * Columns in string mode valid layout are 'attribute:format:header:footer(TODO)'.
 *
 * ~~~
 *
 * \youmbe\phpword\Word::export([
 *  	'models' => Post::find()->all(),
 *     	'columns' => [
 *     		'author.name:text:Author Name',
 *     		[
 *     				'attribute' => 'content',
 *     				'header' => 'Content Post',
 *     				'format' => 'text',
 *     				'value' => function($model) {
 *     					return ExampleClass::removeText('example', $model->content);
 *     				},
 *     		],
 *     		'like_it:text:Reader like this content',
 *     		'created_at:datetime',
 *     		[
 *     				'attribute' => 'updated_at',
 *     				'format' => 'date',
 *     		],
 *     	],
 *     	'headers' => [
 *     		'created_at' => 'Date Created Content',
 * 		],
 * ]);
 *
 * ~~~
 *
 *
 * Import file excel and return into an array.
 *
 * ~~~
 *
 * $data = \youmbe\phpword\Word::import($fileName, $config); // $config is an optional
 *
 * $data = \youmbe\phpword\Word::widget([
 * 		'mode' => 'import',
 * 		'fileName' => $fileName,
 * 		'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
 * 		'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
 * 		'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
 * ]);
 *
 * $data = \youmbe\phpword\Word::import($fileName, [
 * 		'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
 * 		'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
 * 		'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
 * 	]);
 *
 * // import data with multiple file.
 *
 * $data = \youmbe\phpword\Word::widget([
 * 		'mode' => 'import',
 * 		'fileName' => [
 * 			'file1' => $fileName1,
 * 			'file2' => $fileName2,
 * 			'file3' => $fileName3,
 * 		],
 * 		'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
 * 		'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
 * 		'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
 * ]);
 *
 * $data = \youmbe\phpword\Word::import([
 * 			'file1' => $fileName1,
 * 			'file2' => $fileName2,
 * 			'file3' => $fileName3,
 * 		], [
 * 		'setFirstRecordAsKeys' => true, // if you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
 * 		'setIndexSheetByName' => true, // set this if your excel data with multiple worksheet, the index of array will be set with the sheet name. If this not set, the index will use numeric.
 * 		'getOnlySheet' => 'sheet1', // you can set this property if you want to get the specified sheet from the excel data with multiple worksheet.
 * 	]);
 *
 * ~~~
 *
 * Result example from the code on the top :
 *
 * ~~~
 *
 * // only one sheet or specified sheet.
 *
 * Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2));
 *
 * // data with multiple worksheet
 *
 * Array([Sheet1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2)),
 * [Sheet2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2)));
 *
 * // data with multiple file and specified sheet or only one worksheet
 *
 * Array([file1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2)),
 * [file2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2)));
 *
 * // data with multiple file and multiple worksheet
 *
 * Array([file1] => Array([Sheet1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2)),
 * [Sheet2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2))),
 * [file2] => Array([Sheet1] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2)),
 * [Sheet2] => Array([0] => Array([name] => Anam, [email] => moh.khoirul.anaam@gmail.com, [framework interest] => Yii2),
 * [1] => Array([name] => Example, [email] => example@youmbesoft.com, [framework interest] => Yii2))));
 *
 * ~~~
 *
 * @property string $mode is an export mode or import mode. valid value are 'export' and 'import'
 * @property boolean $isMultipleSection for set the export excel with multiple sheet.
 * @property array $properties for set property on the excel object.
 * @property array $models Model object or DataProvider object with much data.
 * @property array $columns to get the attributes from the model, this valid value only the exist attribute on the model.
 * If this is not set, then all attribute of the model will be set as columns.
 * @property array $headers to set the header column on first line. Set this if want to custom header.
 * If not set, the header will get attributes label of model attributes.
 * @property string|array $fileName is a name for file name to export or import. Multiple file name only use for import mode, not work if you use the export mode.
 * @property string $savePath is a directory to save the file or you can blank this to set the file as attachment.
 * @property string $format for excel to export. Valid value are 'Xls','Xlsx','Xml','Ods','Slk','Gnumeric','Csv', and 'Html'.
 * @property boolean $setFirstTitle to set the title column on the first line. The columns will have a header on the first line.
 * @property boolean $asAttachment to set the file excel to download mode.
 * @property boolean $setFirstRecordAsKeys to set the first record on excel file to a keys of array per line.
 * If you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
 * @property boolean $setIndexSheetByName to set the sheet index by sheet name or array result if the sheet not only one
 * @property string $getOnlySheet is a sheet name to getting the data. This is only get the sheet with same name.
 * @property array|Formatter $formatter the formatter used to format model attribute values into displayable texts.
 * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
 * instance. If this property is not set, the "formatter" application component will be used.
 *
 * @author Moh Khoirul Anam <moh.khoirul.anaam@gmail.com>
 * @copyright 2014
 * @since 1
 */
class ContractWord extends \yii\base\Widget {

    // Border style
    const BORDER_NONE = 'none';
    const BORDER_DASHDOT = 'dashDot';
    const BORDER_DASHDOTDOT = 'dashDotDot';
    const BORDER_DASHED = 'dashed';
    const BORDER_DOTTED = 'dotted';
    const BORDER_DOUBLE = 'double';
    const BORDER_HAIR = 'hair';
    const BORDER_MEDIUM = 'medium';
    const BORDER_MEDIUMDASHDOT = 'mediumDashDot';
    const BORDER_MEDIUMDASHDOTDOT = 'mediumDashDotDot';
    const BORDER_MEDIUMDASHED = 'mediumDashed';
    const BORDER_SLANTDASHDOT = 'slantDashDot';
    const BORDER_THICK = 'thick';
    const BORDER_THIN = 'thin';
    // Colors
    const COLOR_BLACK = 'FF000000';
    const COLOR_WHITE = 'FFFFFFFF';
    const COLOR_RED = 'FFFF0000';
    const COLOR_DARKRED = 'FF800000';
    const COLOR_BLUE = 'FF0000FF';
    const COLOR_DARKBLUE = 'FF000080';
    const COLOR_GREEN = 'FF00FF00';
    const COLOR_DARKGREEN = 'FF008000';
    const COLOR_YELLOW = 'FFFFFF00';
    const COLOR_DARKYELLOW = 'FF808000';
    // Horizontal alignment styles
    const HORIZONTAL_GENERAL = 'general';
    const HORIZONTAL_LEFT = 'left';
    const HORIZONTAL_RIGHT = 'right';
    const HORIZONTAL_CENTER = 'center';
    const HORIZONTAL_CENTER_CONTINUOUS = 'centerContinuous';
    const HORIZONTAL_JUSTIFY = 'justify';
    const HORIZONTAL_FILL = 'fill';
    const HORIZONTAL_DISTRIBUTED = 'distributed'; // Word2007 only
    // Vertical alignment styles
    const VERTICAL_BOTTOM = 'bottom';
    const VERTICAL_TOP = 'top';
    const VERTICAL_CENTER = 'center';
    const VERTICAL_JUSTIFY = 'justify';
    const VERTICAL_DISTRIBUTED = 'distributed'; // Word2007 only
    // Read order
    const READORDER_CONTEXT = 0;
    const READORDER_LTR = 1;
    const READORDER_RTL = 2;
    // Fill types
    const FILL_NONE = 'none';
    const FILL_SOLID = 'solid';
    const FILL_GRADIENT_LINEAR = 'linear';
    const FILL_GRADIENT_PATH = 'path';
    const FILL_PATTERN_DARKDOWN = 'darkDown';
    const FILL_PATTERN_DARKGRAY = 'darkGray';
    const FILL_PATTERN_DARKGRID = 'darkGrid';
    const FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal';
    const FILL_PATTERN_DARKTRELLIS = 'darkTrellis';
    const FILL_PATTERN_DARKUP = 'darkUp';
    const FILL_PATTERN_DARKVERTICAL = 'darkVertical';
    const FILL_PATTERN_GRAY0625 = 'gray0625';
    const FILL_PATTERN_GRAY125 = 'gray125';
    const FILL_PATTERN_LIGHTDOWN = 'lightDown';
    const FILL_PATTERN_LIGHTGRAY = 'lightGray';
    const FILL_PATTERN_LIGHTGRID = 'lightGrid';
    const FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal';
    const FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis';
    const FILL_PATTERN_LIGHTUP = 'lightUp';
    const FILL_PATTERN_LIGHTVERTICAL = 'lightVertical';
    const FILL_PATTERN_MEDIUMGRAY = 'mediumGray';
    // Pre-defined formats
    const FORMAT_GENERAL = 'General';
    const FORMAT_TEXT = '@';
    const FORMAT_NUMBER = '0';
    const FORMAT_NUMBER_00 = '0.00';
    const FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00';
    const FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-';
    const FORMAT_PERCENTAGE = '0%';
    const FORMAT_PERCENTAGE_00 = '0.00%';
    const FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd';
    const FORMAT_DATE_YYYYMMDD = 'yy-mm-dd';
    const FORMAT_DATE_DDMMYYYY = 'dd/mm/yy';
    const FORMAT_DATE_DMYSLASH = 'd/m/yy';
    const FORMAT_DATE_DMYMINUS = 'd-m-yy';
    const FORMAT_DATE_DMMINUS = 'd-m';
    const FORMAT_DATE_MYMINUS = 'm-yy';
    const FORMAT_DATE_XLSX14 = 'mm-dd-yy';
    const FORMAT_DATE_XLSX15 = 'd-mmm-yy';
    const FORMAT_DATE_XLSX16 = 'd-mmm';
    const FORMAT_DATE_XLSX17 = 'mmm-yy';
    const FORMAT_DATE_XLSX22 = 'm/d/yy h:mm';
    const FORMAT_DATE_DATETIME = 'd/m/yy h:mm';
    const FORMAT_DATE_TIME1 = 'h:mm AM/PM';
    const FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM';
    const FORMAT_DATE_TIME3 = 'h:mm';
    const FORMAT_DATE_TIME4 = 'h:mm:ss';
    const FORMAT_DATE_TIME5 = 'mm:ss';
    const FORMAT_DATE_TIME6 = 'h:mm:ss';
    const FORMAT_DATE_TIME7 = 'i:s.S';
    const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
    const FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd;@';
    const FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-';
    const FORMAT_CURRENCY_USD = '$#,##0_-';
    const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0.00_-"€"';
    const FORMAT_CURRENCY_EUR = '#,##0_-"€"';

    /**
     * @var string mode is an export mode or import mode. valid value are 'export' and 'import'.
     */
    public $mode = 'export';

    /**
     * @var boolean for set the export excel with multiple sheet.
     */
    public $isMultipleSection = false;

    /**
     * @var array properties for set property on the excel object.
     */
    public $properties = [
        'creator' => 'My name',
        'company' => 'My factory',
        'title' => 'My title',
        'description' => 'My description',
        'category' => 'My category',
        'lastModifiedBy' => 'My name',
        'subject' => 'My subject',
        'keywords' => 'my, key, word',
    ];

    /**
     * @var int 创建时间
     */
    public $created_at;

    /**
     * @var int 修改时间
     */
    public $updated_at;

    /**
     * @var Model object or DataProvider object with much data.
     */
    public $model;

    /**
     * @var array columns to get the attributes from the model, this valid value only the exist attribute on the model.
     * If this is not set, then all attribute of the model will be set as columns.
     */
    public $columns = [];

    /**
     * @var array header to set the header column on first line. Set this if want to custom header.
     * If not set, the header will get attributes label of model attributes.
     */
    public $headers = [];

    /**
     * @var string|array name for file name to export or save.
     */
    public $fileName;

    /**
     * @var string save path is a directory to save the file or you can blank this to set the file as attachment.
     */
    public $savePath;

    /**
     * @var string format for excel to export. Valid value are 'Xls','Xlsx','Xml','Ods','Slk','Gnumeric','Csv', and 'Html'.
     */
    public $format;

    /**
     * @var boolean to set the title column on the first line.
     */
    public $setFirstTitle = true;

    /**
     * @var boolean to set the file excel to download mode.
     */
    public $asAttachment = false;

    /**
     * @var boolean to set the first record on excel file to a keys of array per line.
     * If you want to set the keys of record column with first record, if it not set, the header with use the alphabet column on excel.
     */
    public $setFirstRecordAsKeys = true;

    /**
     * @var boolean to set the sheet index by sheet name or array result if the sheet not only one.
     */
    public $setIndexSheetByName = false;

    /**
     * @var string sheetname to getting. This is only get the sheet with same name.
     */
    public $getOnlySheet;

    /**
     * @var boolean to set the import data will return as array.
     */
    public $asArray;

    /**
     * @var array to unread record by index number.
     */
    public $leaveRecordByIndex = [];

    /**
     * @var array to read record by index, other will leave.
     */
    public $getOnlyRecordByIndex = [];

    /**
     * @var array|Formatter the formatter used to format model attribute values into displayable texts.
     * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
     * instance. If this property is not set, the "formatter" application component will be used.
     */
    public $formatter;

    /**
     * @var boolean define the column autosize
     */
    public $autoSize = false;

    /**
     * @var boolean if true, this writer pre-calculates all formulas in the spreadsheet. This can be slow on large spreadsheets, and maybe even unwanted.
     */
    public $preCalculationFormula = false;

    /**
     * @var boolean Because of a bug in the Office2003 compatibility pack, there can be some small issues when opening Xlsx spreadsheets (mostly related to formula calculation)
     */
    public $compatibilityOffice2003 = false;

    /**
     * (non-PHPdoc)
     * @see \yii\base\Object::init()
     */
    public function init() {
        parent::init();
        if ($this->formatter == null) {
            $this->formatter = Yii::$app->getFormatter();
        } elseif (is_array($this->formatter)) {
            $this->formatter = Yii::createObject($this->formatter);
        }
        if (!$this->formatter instanceof Formatter) {
            throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
        }
        if (!$this->format) {
            $this->format = 'Word2007';
        }
    }

    /**
     * Setting data from models
     */
    public function executeColumns(&$activeSheet = null, $models, $columns = [], $headers = []) {
        if ($activeSheet == null) {
            $activeSheet = $this->activeSheet;
        }
        $hasHeader = false;
        $row = 1;
        $char = 26;
        foreach ($models as $model) {
            if (empty($columns)) {
                $columns = $model->attributes();
            }
            if ($this->setFirstTitle && !$hasHeader) {
                $isPlus = false;
                $colplus = 0;
                $colnum = 1;
                foreach ($columns as $key => $column) {
                    $col = '';
                    if ($colnum > $char) {
                        $colplus += 1;
                        $colnum = 1;
                        $isPlus = true;
                    }
                    if ($isPlus) {
                        $col .= chr(64 + $colplus);
                    }
                    $col .= chr(64 + $colnum);
                    $header = '';
                    if (is_array($column)) {
                        if (isset($column['header'])) {
                            $header = $column['header'];
                        } elseif (isset($column['attribute']) && isset($headers[$column['attribute']])) {
                            $header = $headers[$column['attribute']];
                        } elseif (isset($column['attribute'])) {
                            $header = $model->getAttributeLabel($column['attribute']);
                        } elseif (isset($column['cellFormat']) && is_array($column['cellFormat'])) {
                            $activeSheet->getStyle($col . $row)->applyFromArray($column['cellFormat']);
                        }
                    } else {
                        $header = $model->getAttributeLabel($column);
                    }
                    $activeSheet->setCellValue($col . $row, $header);
                    $colnum++;
                }
                $hasHeader = true;
                $row++;
            }
            $isPlus = false;
            $colplus = 0;
            $colnum = 1;
            foreach ($columns as $key => $column) {
                $col = '';
                if ($colnum > $char) {
                    $colplus++;
                    $colnum = 1;
                    $isPlus = true;
                }
                if ($isPlus) {
                    $col .= chr(64 + $colplus);
                }
                $col .= chr(64 + $colnum);
                if (is_array($column)) {
                    $column_value = $this->executeGetColumnData($model, $column);
                    if (isset($column['cellFormat']) && is_array($column['cellFormat'])) {
                        $activeSheet->getStyle($col . $row)->applyFromArray($column['cellFormat']);
                    }
                } else {
                    $column_value = $this->executeGetColumnData($model, ['attribute' => $column]);
                }
                $activeSheet->setCellValue($col . $row, $column_value);
                $colnum++;
            }
            $row++;

            if ($this->autoSize) {
                foreach (range(0, $colnum) as $col) {
                    $activeSheet->getColumnDimensionByColumn($col)->setAutoSize(true);
                }
            }
        }
    }

    /**
     * Setting label or keys on every record if setFirstRecordAsKeys is true.
     * @param array $sheetData
     * @return multitype:multitype:array
     */
    public function executeArrayLabel($sheetData) {
        $keys = ArrayHelper::remove($sheetData, '1');

        $new_data = [];

        foreach ($sheetData as $values) {
            $new_data[] = array_combine($keys, $values);
        }

        return $new_data;
    }

    /**
     * Leave record with same index number.
     * @param array $sheetData
     * @param array $index
     * @return array
     */
    public function executeLeaveRecords($sheetData = [], $index = []) {
        foreach ($sheetData as $key => $data) {
            if (in_array($key, $index)) {
                unset($sheetData[$key]);
            }
        }
        return $sheetData;
    }

    /**
     * Read record with same index number.
     * @param array $sheetData
     * @param array $index
     * @return array
     */
    public function executeGetOnlyRecords($sheetData = [], $index = []) {
        foreach ($sheetData as $key => $data) {
            if (!in_array($key, $index)) {
                unset($sheetData[$key]);
            }
        }
        return $sheetData;
    }

    /**
     * Getting column value.
     * @param Model $model
     * @param array $params
     * @return Ambigous <NULL, string, mixed>
     */
    public function executeGetColumnData($model, $params = []) {
        $value = null;
        if (isset($params['value']) && $params['value'] !== null) {
            if (is_string($params['value'])) {
                $value = ArrayHelper::getValue($model, $params['value']);
            } else {
                $value = call_user_func($params['value'], $model, $this);
            }
        } elseif (isset($params['attribute']) && $params['attribute'] !== null) {
            $value = ArrayHelper::getValue($model, $params['attribute']);
        }

        if (isset($params['format']) && $params['format'] != null)
            $value = $this->formatter()->format($value, $params['format']);

        return $value;
    }

    /**
     * Populating columns for checking the column is string or array. if is string this will be checking have a formatter or header.
     * @param array $columns
     * @throws InvalidParamException
     * @return multitype:multitype:array
     */
    public function populateColumns($columns = []) {
        $_columns = [];
        foreach ($columns as $key => $value) {
            if (is_string($value)) {
                $value_log = explode(':', $value);
                $_columns[$key] = ['attribute' => $value_log[0]];

                if (isset($value_log[1]) && $value_log[1] !== null) {
                    $_columns[$key]['format'] = $value_log[1];
                }

                if (isset($value_log[2]) && $value_log[2] !== null) {
                    $_columns[$key]['header'] = $value_log[2];
                }
            } elseif (is_array($value)) {
                if (!isset($value['attribute']) && !isset($value['value'])) {
                    throw new \InvalidArgumentException('Attribute or Value must be defined.');
                }
                $_columns[$key] = $value;
            }
        }

        return $_columns;
    }

    /**
     * Formatter for i18n.
     * @return Formatter
     */
    public function formatter() {
        if (!isset($this->formatter)) {
            $this->formatter = Yii::$app->getFormatter();
        }

        return $this->formatter;
    }

    /**
     * Setting header to download generated file xls
     */
    public function setHeaders() {
        if ($this->format == 'Word2007') {
            header("Content-Description: File Transfer");
            header('Content-Disposition: attachment;filename="' . $this->getFileName() . '"');
            header('Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document');
            header('Content-Transfer-Encoding: binary');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header('Expires: 0');
        }
    }

    /**
     * Getting the file name of exporting xls file
     * @return string
     */
    public function getFileName() {
        return WordIOFactory::buildFileName($this->fileName, $this->format);
    }

    /**
     * Setting properties for word file
     * @param PHPWord $objectWord
     * @param array $properties
     */
    public function properties(&$objectWord, $properties = []) {
        $docInfo = $objectWord->getDocInfo();
        foreach ($properties as $key => $value) {
            $keyname = "set" . ucfirst($key);
            $docInfo->{$keyname}($value);
        }
        $docInfo->setCreated($this->created_at ?: time());
        $docInfo->setModified($this->updated_at ?: time());
    }

    /**
     * Setting properties for word file
     * @param PHPWord $objectWord
     * @param array $styles
     */
    public function setStyles(&$objectWord, $styles = []) {
        //默认样式
        $objectWord->setDefaultFontName('仿宋');
        $objectWord->setDefaultFontSize(14);
        $objectWord->setDefaultParagraphStyle([
            'indentation' => [
                'firstLine' => Font::pointSizeToTwips(28),
                'left' => 0,
            ],
            'spacing' => Font::pointSizeToTwips(24),
            'spacingLineRule' => LineSpacingRule::EXACT,
        ]);

        //标题样式
        $objectWord->addTitleStyle(1, [
            'name' => '华文中宋',
            'size' => 28,
            'bold' => true
        ], [
            'indentation' => [
                'firstLine' => 0,
            ],
            'spacing' => 1,
            'spaceBefore' => Font::pointSizeToTwips(28),
            'spaceAfter' => Font::pointSizeToTwips(28),
            'alignment' => Jc::CENTER,
            'textAlignment' => TextAlignment::CENTER,
        ]);

        //自定义样式
        $objectWord->addFontStyle('myOwnStyle', array('color' => 'FF0000'));
        $objectWord->addParagraphStyle('P-Style', array('spaceAfter' => 95));
    }

    /**
     * saving the xls file to download or to path
     * @param PhpWord $word
     */
    public function writeFile($word) {
        if (!isset($this->format)) {
            $this->format = 'Word2007';
        }

        if (!isset($this->format)) {
            $this->format = 'Word2007';
        }
        $filename = $this->getFileName();
        if ($this->asAttachment) {
            $word->save($filename, $this->format, true);
        } else {
            if (isset($this->savePath) && $this->savePath != null) {
                $path = $this->savePath . '/' . $filename;
                $objectwriter = WordIOFactory::createWriter($word, $this->format);
                $objectwriter->save($path);
            } else {
                $word->save($filename, $this->format, false);
            }
        }
        exit();
        //        $objectwriter = WordIOFactory::createWriter($word, $this->format);
        //        $path = 'php://output';
        //        if (isset($this->savePath) && $this->savePath != null) {
        //            $path = $this->savePath . '/' . $this->getFileName();
        //        }
        //        $objectwriter->save($path);
        //        if ($path == 'php://output') {
        //            exit();
        //        }
    }

    /**
     * (non-PHPdoc)
     * @see \yii\base\Widget::run()
     */
    public function run2() {
        $sheet = new Spreadsheet();

        if (!isset($this->models)) {
            throw new InvalidConfigException('Config models must be set');
        }

        if (isset($this->properties)) {
            $this->properties($sheet, $this->properties);
        }


        if ($this->isMultipleSection) {
            $index = 0;
            $worksheet = [];
            foreach ($this->models as $title => $models) {
                $sheet->createSection($index);
                $sheet->getSheet($index)->setTitle($title);
                $worksheet[$index] = $sheet->getSheet($index);
                $columns = isset($this->columns[$title]) ? $this->columns[$title] : [];
                $headers = isset($this->headers[$title]) ? $this->headers[$title] : [];
                $this->executeColumns($worksheet[$index], $models, $this->populateColumns($columns), $headers);
                $index++;
            }
        } else {
            $worksheet = $sheet->getActiveSheet();
            $this->executeColumns($worksheet, $this->models, isset($this->columns) ? $this->populateColumns($this->columns) : [], isset($this->headers) ? $this->headers : []);
        }

        if ($this->asAttachment) {
            $this->setHeaders();
        }
        $this->writeFile($sheet);
        $sheet->disconnectWorksheets();
        unset($sheet);
    }

    public function executeList($section) {
        $data = [
            [
                'title' => '定义',
                'items' => [
                    [
                        'title' => '“项目”是指乙方按照《开发服务说明书》中的约定，向甲方客户提供服务的具体项目。'
                    ],
                    [
                        'title' => '“关联公司”是指控制、受控于本合同一方或同本合同一方共同受某一公司控制的实体。'
                    ],
                    [
                        'title' => '“本合同” 是指本《委托开发服务合同》及其所有附件。'
                    ],
                    [
                        'title' => '“技术服务”或“服务”是指乙方按本合同及其所有附件的约定，为甲方客户所做的工作。'
                    ],
                    [
                        'title' => '“可交付成果物”是指本合同实施过程中产出的工作成果。'
                    ],
                    [
                        'title' => '“保密信息”是指双方在讨论、订立及履行本合同过程中向另一方提供的全部技术和商业信息，以及本合同及其所有附件的内容及可交付成果物（如有）。'
                    ],
                    [
                        'title' => '“服务费用”是指依照本合同中约定的服务费用，已包括乙方应缴纳的税款。'
                    ],
                    [
                        'title' => '“税款”是指根据中华人民共和国法律规定对可交付成果物和/或服务应征收的税款。'
                    ],
                ],
            ],
            [
                'title' => '项目的实施',
                'items' => [
                    [
                        'title' => '乙方应按本合同及《开发服务说明书》中的相关约定，向甲方提供相应的技术开发与维护服务和/或可交付成果物。',
                    ],
                    [
                        'title' => '项目的内容',
                        'content' => '以《开发服务说明书》中的约定的开发项目内容及要求为准。',
                    ],
                    [
                        'title' => '服务周期与开发进度',
                        'content' => '以《开发服务说明书》中的约定为准。',
                    ],
                    [
                        'title' => '验收标准和验收方式',
                        'content' => '以《开发服务说明书》中的约定为准。',
                    ],
                ],
            ]
        ];
        static::dumpItem($section, $data, [
            'listType' => ListItemStyle::TYPE_NUMBER_NESTED
        ]);
    }

    public static function dumpItem(&$section, $data, $listStyle, $level = 0) {
        foreach ($data as $item) {
            $title = ArrayHelper::getValue($item, 'title', '');
            $section->addListItem($title, $level, 'myOwnStyle', $listStyle, 'P-Style');
            $content = ArrayHelper::getValue($item, 'content', '');
            if ($content) {
                $section->addPreserveText($content);
            }
            $items = ArrayHelper::getValue($item, 'items', []);
            if (empty($items)) {
                continue;
            }
            static::dumpItem($section, $items, $listStyle, $level + 1);
        }
    }

    public function run() {
        // Creating the new document...
        $phpWord = new PhpWord();

        if (isset($this->properties)) {
            $this->properties($phpWord, $this->properties);
        }
        //设置文档使用样式
        $this->setStyles($phpWord);

        $model = $this->model;
        if ($model) {
            
        }

        if ($this->isMultipleSection) {
            $index = 0;
            $worksheet = [];
            foreach ($this->models as $model) {
                $phpWord->addSection();
                $sheet->getSheet($index)->setTitle($title);
                $worksheet[$index] = $sheet->getSheet($index);
                $columns = isset($this->columns[$title]) ? $this->columns[$title] : [];
                $headers = isset($this->headers[$title]) ? $this->headers[$title] : [];
                $this->executeColumns($worksheet[$index], $models, $this->populateColumns($columns), $headers);
                $index++;
            }
        } else {
            $worksheet = $sheet->getActiveSheet();
            $this->executeColumns($worksheet, $this->models, isset($this->columns) ? $this->populateColumns($this->columns) : [], isset($this->headers) ? $this->headers : []);
        }

        $section1 = $phpWord->addSection();

        $section1->addTitle('技术服务合同', 1);

        // Adding Text element to the Section having font styled by default...
        $section1->addText('"Learn from yesterday, live for today, hope for tomorrow. '
        . 'The important thing is not to stop questioning." '
        . '(Albert Einstein)');

        $this->executeList($section1);

        $txt = '1111';
        $depth = 0;
        $font = null;
        $list = new \PhpOffice\PhpWord\Style\ListItem();
        $list->setListType(1);
        $para = null;

        $section1->addListItem($txt, $depth, $font, $list, $para);

        /* Note: any element you append to a document must reside inside of a Section. */

        // Adding an empty Section to the document...
        $section = $phpWord->addSection();
        // Adding Text element to the Section having font styled by default...
        $section->addText('"Learn from yesterday, live for today, hope for tomorrow. ' . 'The important thing is not to stop questioning." ' . '(Albert Einstein)');

        /*
         * Note: it's possible to customize font style of the Text element you add in three ways:
         * - inline;
         * - using named font style (new font style object will be implicitly created);
         * - using explicitly created font style object.
         */
        // Adding Text element with font customized inline...
        $section->addText('"Great achievement is usually born of great sacrifice, ' . 'and is never the result of selfishness." ' . '(Napoleon Hill)', [
            'name' => 'Tahoma',
            'size' => 10
        ]);

        // Adding Text element with font customized using named font style...
        $fontStyleName = 'oneUserDefinedStyle';
        $phpWord->addFontStyle($fontStyleName, [
            'name' => 'Tahoma',
            'size' => 10,
            'color' => '1B2232',
            'bold' => true
        ]);
        $section->addText('"The greatest accomplishment is not in never falling, ' . 'but in rising again after you fall." ' . '(Vince Lombardi)', $fontStyleName);

        // Adding Text element with font customized using explicitly created font style object...
        $fontStyle = new \PhpOffice\PhpWord\Style\Font();
        $fontStyle->setBold(true);
        $fontStyle->setName('Tahoma');
        $fontStyle->setSize(13);
        $myTextElement = $section->addText('"Believe you can and you\'re halfway there." (Theodor Roosevelt)');
        $myTextElement->setFontStyle($fontStyle);

        $this->writeFile($phpWord);
        unset($phpWord);
    }

    public function demolist($section) {
        //添加列表元素
        $section->addListItem('ListItem 1', 0);
        $section->addListItem('ListItem 2', 0);
        $section->addListItem('ListItem 3', 0);
        $section->addTextBreak(2);

        $section->addListItem('ListItem 1', 0);
        $section->addListItem('ListItem 1.1', 1);
        $section->addListItem('ListItem 1.2', 1);
        $section->addListItem('ListItem 1.3(styled)', 1, array('bold' => true));
        $section->addListItem('ListItem 1.3.1', 2);
        $section->addListItem('ListItem 1.3.2', 2);
        $section->addTextBreak(2);

        $listStyle = array('listType' => ListItemStyle::TYPE_NUMBER);
        $section->addListItem('ListItem 1', 0, null, $listStyle);
        $section->addListItem('ListItem 2', 0, null, $listStyle);
        $section->addListItem('ListItem 3', 0, null, $listStyle);
        $section->addTextBreak(2);


        $listStyle = array('listType' => ListItemStyle::TYPE_NUMBER_NESTED);
        $section->addListItem('ListItem 1', 0, 'myOwnStyle', $listStyle, 'P-Style');
        $section->addListItem('ListItem 2', 0, 'myOwnStyle', $listStyle, 'P-Style');
        $section->addListItem('ListItem 3', 1, 'myOwnStyle', $listStyle, 'P-Style');
        $section->addListItem('ListItem 4', 1, 'myOwnStyle', $listStyle, 'P-Style');
        $section->addListItem('ListItem 5', 2, 'myOwnStyle', $listStyle, 'P-Style');
        $section->addListItem('ListItem 6', 2, 'myOwnStyle', $listStyle, 'P-Style');
        $section->addListItem('ListItem 7', 1, 'myOwnStyle', $listStyle, 'P-Style');
        $section->addListItem('ListItem 8', 0, 'myOwnStyle', $listStyle, 'P-Style');
    }

}
